#ifndef SH_ENDIAN_ENDIAN_HPP
#define SH_ENDIAN_ENDIAN_HPP

#include "../base.hpp"
#include "../singleton.hpp"
#include <type_traits>
#include <algorithm>


namespace sh {

enum class Endian : int { Little, Big };
//enum class ByteOrder : int { LittleEndian, BigEndian };
using ByteOrder = Endian;

struct DataConvert {
    DataConvert();
    DataConvert(const DataConvert& rhs);
    DataConvert& operator= (const DataConvert& rhs);
    ~DataConvert();

    inline Endian getSystemEndian() const { return systemEndian; }
    inline Endian getDefaultEndian() const { return defaultEndian; }
    void setDefaultEndian(Endian endian) { defaultEndian = endian; }

    template<class T>
    size_t hex2data(T& data, const uint8_t* buf, size_t index, size_t len, Endian endian);

    template<class T>
    size_t data2hex(const T& data, uint8_t* buf, size_t index, size_t len, Endian endian);

    template<class T>
    inline size_t hex2data(T& data, const uint8_t* buf, size_t index, size_t len) {
        return hex2data(data, buf, index, len, defaultEndian);
    }

    template<class T>
    inline size_t data2hex(const T& data, uint8_t* buf, size_t index, size_t len) {
        return data2hex(data, buf, index, len, defaultEndian);
    }

private:
    const Endian systemEndian;
    Endian defaultEndian;
};


// Global function with DataConvert
inline Endian getSystemEndian() { return Singleton<DataConvert>::getInstance()->getSystemEndian(); }
inline Endian getDefaultEndian() { return Singleton<DataConvert>::getInstance()->getDefaultEndian(); }
inline void setDefaultEndian(Endian endian) { Singleton<DataConvert>::getInstance()->setDefaultEndian(endian); }
template<class T>
inline size_t
hex2data(T& data, const uint8_t* buf, size_t index, size_t len, Endian endian) {
    return Singleton<DataConvert>::getInstance()->hex2data(data, buf, index, len, endian);
}
template<class T>
inline size_t
hex2data(T& data, const uint8_t* buf, size_t index, size_t len) {
    return Singleton<DataConvert>::getInstance()->hex2data(data, buf, index, len);
}

template<class T>
inline size_t 
data2hex(const T& data, uint8_t* buf, size_t index, size_t len, Endian endian) {
    return Singleton<DataConvert>::getInstance()->data2hex(data, buf, index, len, endian);
}

template<class T>
inline size_t 
data2hex(const T& data, uint8_t* buf, size_t index, size_t len) {
    return Singleton<DataConvert>::getInstance()->data2hex(data, buf, index, len);
}

/*
* //////////////////////////////////////////////////////////////////////////////////////////
*
*
*
* //////////////////////////////////////////////////////////////////////////////////////////
*/

//inline const Endian DataConvert::systemEndian = []() {
//    union T { char c[2]; short s; } t; t.s = 1;
//    return static_cast<Endian>(t.c[1]);
//}();

inline Endian TestSystemEndian() {
    //constexpr uint16_t s = 1;
    //return static_cast<Endian>(*(reinterpret_cast<const uint8_t*>(&s)));

    union T { char c[2]; short s; } t; t.s = 1;
    return static_cast<Endian>(t.c[1]);
}

inline DataConvert::DataConvert() : systemEndian{TestSystemEndian()}, defaultEndian{systemEndian} { }
inline DataConvert::DataConvert(const DataConvert& rhs) : systemEndian{ rhs.systemEndian } , defaultEndian{ rhs.defaultEndian } {}
inline DataConvert& DataConvert::operator= (const DataConvert& rhs) { defaultEndian = rhs.defaultEndian; return *this; }
inline DataConvert::~DataConvert() { }

template<class T>
inline size_t
DataConvert::hex2data(T& data, const uint8_t* buf, size_t index, size_t len, Endian endian) {
    static_assert(std::is_arithmetic<T>::value, "data must arithmetic");
    constexpr size_t size = sizeof(T);
    if (index+size > len) throw "cross the border";
    char tmp[size]{};
    memcpy(tmp, buf+index, size);
    if(systemEndian != endian) std::reverse(tmp, tmp+size);
    data = *reinterpret_cast<T*>(tmp);
    // std::cout << data << std::endl;
    return size;
}

template<class T>
inline size_t
DataConvert::data2hex(T const& data, uint8_t* buf, size_t index, size_t len, Endian endian) {
    static_assert(std::is_arithmetic<T>::value, "data must arithmetic");
    constexpr size_t size = sizeof(T);
    if (index+size > len) throw "cross the border";
    auto *tmp = reinterpret_cast<const char*>(&data);
    memcpy(buf+index, tmp, size);
    if(systemEndian != endian) std::reverse(buf+index, buf+index+size);
    return size;
}

} // namespace sh

#endif // SH_ENDIAN_ENDIAN_HPP
